Material Design的Motion由四種模式組成,用於在元件或全屏檢視之間過渡轉換動畫,這些模式主要在透過加強使用者介面元素之間的關係來幫助使用者瀏覽和理解應用程式,過渡轉換是一個協調的動畫序列,序列中的UI元素被歸類為傳出、傳入或持久,在介面適應時保持使用者的焦點。
AndroidX Transition library (androidx.transition)
Android Transition Framework (android.transition):
app module's build.gradle
dependencies.
MDC-Android library implementation 'com.google.android.material:material:1.2.0'
此模式在兩個UI元素之間建立可見的動畫效果,MaterialContainerTransform是一個shared element transition(共享元素過渡),設計應用程式中的Activity過渡透過共同元素之間的連動和轉換,在不同狀態之間提供視覺動畫效果。
「共享元素指」的是start View或ViewGroup,然後將其大小和形狀轉換為end View或ViewGroup,這些開始和結束View或ViewGroup的“共享元素”在轉換時,它們的內容被交換以建立過渡動畫。
Activity 和 Window過渡需要使用com.google.android.material.transition.platform
提供的Android框架Transition,並且只能在API級別21及更高版本上可用。
準備兩個Class,和兩個 layout
Activity A’s Layout:
在Activity的layout中,識別要用作容器轉換概述中描述的“共享元素”的開始View。 給View一個Transition名稱android:transitionName="shared_element_container"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="shared_element_container">
<Button
android:id="@+id/btnTOB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="點擊放大圖案"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/start_view"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="20dp"
android:background="@drawable/ic_emoji_symbols_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnTOB" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity A’s Class
在onCreate做相關設定
transitionName
名稱class TransitionActivityA: AppCompatActivity() {
private lateinit var binding: ActivityABinding
override fun onCreate(savedInstanceState: Bundle?) {
// 1.設定Enable Activity Transitions
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
// 2. 附加用於捕獲此活動的共享元素的Callback
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
// 3. 在整個過渡過程中保持系統欄(狀態欄、導航欄)的永續性。
window.sharedElementsUseOverlay = false
super.onCreate(savedInstanceState)
binding = ActivityABinding.inflate(layoutInflater)
setContentView(binding.root)
// button點擊
binding.btnTOB.setOnClickListener {
// Activity B 中要匹配的轉換名稱
val intent = Intent(this, TransitionActivityB::class.java)
// 透過 ActivityOptions.makeSceneTransitionAnimation()傳遞Bundle和指定的動畫效果的view
val options = ActivityOptions.makeSceneTransitionAnimation(
this, binding.startView, "shared_element_container")
startActivity(intent, options.toBundle())
}
}
}
Activity B’s Layout:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/ic_emoji_symbols_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Activity B’s Class:
在onCreate做相關設定
class TransitionActivityB: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 1. content設定transition name, 開始跟 Activity A 的視圖轉換名稱
findViewById<View>(android.R.id.content).transitionName = "shared_element_container"
// 2. 加上 callback 用來接收Activity A的 container transform
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
// 3. 將此 Activity 的進入和返迴轉換設置為 MaterialContainerTransform
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
addTarget(android.R.id.content)
duration = 500L
}
window.sharedElementReturnTransition = MaterialContainerTransform().apply {
addTarget(android.R.id.content)
duration = 250L
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
}
}
準備Fragment A and Fragment B's layouts、Class
Transition名稱android:transitionName="shared_element_container"
<!--fragment_a.xml-->
<View
android:id="@+id/start_view"
android:transitionName="shared_element_container" />
<!--fragment_b.xml-->
<View
android:id="@+id/end_view"
android:transitionName="shared_element_container" />
FragmentA 在 onCreate method 設定
畫面的共享元素的動畫轉換,將FragmentB新增到/替換Fragment容器之前完成。
class FragmentTransitionA: Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val fragmentB = FragmentTransitionB()
fragmentB.sharedElementEnterTransition = MaterialContainerTransform()
}
}
// 或者也可設定在Fragment B's onCreate method
class FragmentTransitionB: Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition= MaterialContainerTransform()
}
}
FragmentA‘s Class:
新增或替換FragmentB,將共享元素從開始場景新增到Fragment。
childFragmentManager
.beginTransaction()
// 透過transitionName找到 FragmentA 中的 start View 和 FragmentB 中 end View 的 transitionName
.addSharedElement(view, "shared_element_container")
.replace(R.id.fragment_container, fragmentB, "FragmentTransitionB")
.addToBackStack("FragmentTransitionB")
.commit()
執行此Transition時,會注意到轉換開始,Fragment A就會消失。 這是因為FragmentA已從容器中移除。 要在容器轉換播放時“保持”FragmentA,將FragmentA的退出Transition設定為保留。
// 1. 設定Hold()
exitTransition = Hold()
// 2. 設定 MaterialElevationScale
// MaterialElevationScale(false),在進入轉換Fragment時退出縮小FragmentA
exitTransition = MaterialElevationScale(false)
// 在返回轉換Fragment期間重新進入FragmentA時, MaterialElevationScale(true),以放大FragmentA。
reenterTransition = MaterialElevationScale(true)
完成FragmentA‘s Class程式碼
class FragmentTransitionA: Fragment() {
private var _binding : FragmentTransitionABinding? = null
private val binding get() = _binding
private val fragmentB = FragmentTransitionB()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fragmentB.sharedElementEnterTransition = MaterialContainerTransform()
exitTransition = Hold()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTransitionABinding.inflate(layoutInflater, container, false)
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager
.beginTransaction()
// 透過transitionName找到 FragmentA 中的 start View 和 FragmentB 中 end View 的 transitionName
.addSharedElement(view, "shared_element_container")
.replace(R.id.fragment_container, fragmentB, "FragmentTransitionB")
.addToBackStack("FragmentTransitionB")
.commit()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
可以在MaterialContainerTransform上手動設定以下屬性,以自定義動畫的外觀和感覺:
Container transform attributes
Container transform properties
設定動畫轉換的背景顏色開始畫面和結束的畫面
感謝耐心看到這邊~~
今日只講了Motion - Container transform patterns,明日繼續其他。